/*
 *   Copyright 2012, Thomas Kerber
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package milk.jpatch.attribs;

import java.util.ArrayList;
import java.util.List;

import milk.jpatch.CPoolMap;
import milk.jpatch.Util;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.ExceptionTable;

/**
 * Patches an "Exceptions" attribute.
 * 
 * Merges with previous.
 * @author Thomas Kerber
 * @version 1.0.1
 */
public class ExceptionsPatch extends AttributePatch{
    
    private static final long serialVersionUID = -3064317345029686641L;
    
    /**
     * The indexes - in the diffs constant pool - of added exceptions.
     */
    private final int[] addedIndexes;
    /**
     * The names of removed exceptions.
     */
    private final String[] removedNames;
    
    /**
     * Creates the patch.
     * @param addedIndexes The added exceptions indexes.
     * @param removedIndexes The removed exceptions names.
     */
    public ExceptionsPatch(int[] addedIndexes, String[] removedNames){
        this.addedIndexes = addedIndexes;
        this.removedNames = removedNames;
    }
    
    /**
     * Generates.
     * @param old The old attributes.
     * @param new_ The new attributes.
     * @param patches The already generated patches.
     */
    public static void generate(Attribute[] old, Attribute[] new_,
            List<AttributePatch> patches){
        
        ExceptionTable newExceptions = null;
        ExceptionTable oldExceptions = null;
        for(Attribute a : old){
            if(a instanceof ExceptionTable){
                oldExceptions = (ExceptionTable)a;
                break;
            }
        }
        for(Attribute a : new_){
            if(a instanceof ExceptionTable){
                newExceptions = (ExceptionTable)a;
                break;
            }
        }
        
        int[] newIndexes = new int[0];
        String[] newStrings = new String[0];
        if(newExceptions != null){
            newIndexes = newExceptions.getExceptionIndexTable();
            newStrings = newExceptions.getExceptionNames();
        }
        int[] oldIndexes = new int[0];
        String[] oldStrings = new String[0];
        if(oldExceptions != null){
            oldIndexes = oldExceptions.getExceptionIndexTable();
            oldStrings = oldExceptions.getExceptionNames();
        }
        
        List<Integer> removedIndexes = new ArrayList<Integer>();
        List<Integer> addedIndexes = new ArrayList<Integer>();
        // Find added
        outer: for(int i = 0; i < newStrings.length; i++){
            for(int f = 0; f < oldStrings.length; f++){
                if(newStrings[i].equals(oldStrings[f]))
                    continue outer;
            }
            addedIndexes.add(newIndexes[i]);
        }
        // Find removed
        outer: for(int i = 0; i < oldStrings.length; i++){
            for(int f = 0; f < newStrings.length; f++){
                if(newStrings[f].equals(oldStrings[i]))
                    continue outer;
            }
            removedIndexes.add(oldIndexes[i]);
        }
        if(addedIndexes.size() == 0 && removedIndexes.size() == 0)
            return;
        int[] addedInt = new int[addedIndexes.size()];
        String[] removedStr = new String[removedIndexes.size()];
        for(int i = 0; i < addedInt.length; i++)
            addedInt[i] = addedIndexes.get(i);
        
        ConstantPool c = null;
        for(int i = 0; i < removedStr.length; i++){
            if(c == null)
                // Guaranteed to be set as some where apparently "removed"
                // if this point is reached.
                c = old[0].getConstantPool();
            removedStr[i] = c.getConstantString(removedIndexes.get(i),
                    Constants.CONSTANT_Class);
        }
        patches.add(new ExceptionsPatch(addedInt, removedStr));
    }
    
    /**
     * 
     * @return The indexes of added exceptions (in the diff constant pool).
     */
    public int[] getAddedIndexes(){
        return addedIndexes;
    }
    
    /**
     * 
     * @return The names of exceptions removed by this patch.
     */
    public String[] getRemovedNames(){
        return removedNames;
    }
    
    @Override
    public List<Attribute> patch(List<Attribute> attribs, CPoolMap map){
        int[] newIndexes = new int[addedIndexes.length];
        for(int i = 0; i < addedIndexes.length; i++)
            newIndexes[i] = map.get(addedIndexes[i]);
        int[] remIndexes = new int[removedNames.length];
        for(int i = 0; i < removedNames.length; i++)
            remIndexes[i] = Util.findConstantClassIn(map, removedNames[i],
                    true);
        
        
        int index = findName(attribs, "Exceptions");
        int[] oldIndexes;
        if(index == -1) // None previously
            oldIndexes = new int[0];
        else
            oldIndexes = ((ExceptionTable)attribs.get(index)).
                    getExceptionIndexTable();
        
        List<Integer> indexes = new ArrayList<Integer>();
        // Add old indexes not removed
        outer: for(int i : oldIndexes){
            for(int f : remIndexes){
                if(i == f)
                    continue outer;
            }
            indexes.add(i);
        }
        // Add new indexes
        for(int i : newIndexes)
            //TODO: here and other: prevent duplicate additions.
            indexes.add(i);
        
        if(indexes.size() == 0)
            return attribs;
        
        int[] intIndexes = new int[indexes.size()];
        for(int i = 0; i < intIndexes.length; i++)
            intIndexes[i] = indexes.get(i);
        
        ExceptionTable e = new ExceptionTable(
                Util.findConstantStringIn(map, "Exceptions"),
                2 + 2 * indexes.size(),
                intIndexes,
                map.to);
        
        return replaceTypeWith(attribs, "Exceptions", e);
    }
    
}
